package com.geek.web.mvc;

import com.geek.web.mvc.controller.Controller;
import com.geek.web.mvc.controller.PageController;
import com.geek.web.mvc.controller.RestController;
import org.apache.commons.lang.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author 小马哥
 * @author lizhiyang
 * 基于小马哥的基础之上做了一个小扩展, 一个控制器，可以支持多方法
 * @since 1.1
 */
public class FrontControllerServlet extends HttpServlet {

    /**
     * 请求路径和{@link HandlerMethodInfo }映射关系缓存
     */
    private Map<String, HandlerMethodInfo> handleMethodInfoMapping = new HashMap<>();

    /**
     * 请求路径和控制器映射
     */
    private Map<String, Controller> controllersMapping = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        initHandleMethods();
    }

    private void initHandleMethods() {
        ServiceLoader<Controller> serviceLoader = ServiceLoader.load(Controller.class);
        for (Controller controller : serviceLoader) {
            Class<? extends Controller> controllerClass = controller.getClass();
            Path pathFromClass = controllerClass.getAnnotation(Path.class);
            Method[] publicMethods = controllerClass.getMethods();
            // 处理方法支持的 HTTP 方法集合
            for (Method method : publicMethods) {
                Set<String> supportedHttpMethods = findSupportedHttpMethod(method);
                Path annotation = method.getAnnotation(Path.class);
                if (null != annotation) {
                    String newRequestPath = "";
                    if (!annotation.value().equals("/")) {
                        newRequestPath = pathFromClass.value() + annotation.value();
                    } else {
                        newRequestPath = pathFromClass.value();
                    }
                    handleMethodInfoMapping.put(newRequestPath, new HandlerMethodInfo(newRequestPath, method,
                            supportedHttpMethods));
                    controllersMapping.put(newRequestPath, controller);
                }
            }
        }
    }


    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws
            ServletException, IOException {
        // 建立映射关系
        // requestURI = /a/hello/world
        String requestUri = request.getRequestURI();
        String prefixPath = request.getContextPath();
        // contextPath  = /a or "/" or ""
        String requestMappingPath = StringUtils.substringAfter(requestUri, StringUtils.replace(prefixPath, "//", "/"));
        HandlerMethodInfo handlerMethodInfo = handleMethodInfoMapping.get(requestMappingPath);

        try {
            if (null != handlerMethodInfo) {
                String method = request.getMethod();
                boolean contains = handlerMethodInfo.getSupportedHttpMethods().contains(method);
                if (!contains) {
                    //如果是不包含,就表示HTTP不支持该操作
                    response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                    return;
                }
                Method handlerMethod = handlerMethodInfo.getHandlerMethod();
                Controller controller = controllersMapping.get(requestMappingPath);
                if (controller instanceof PageController) {
                    PageController pageController = (PageController) controller;
                    String viewPath = (String) handlerMethod.invoke(pageController, request, response);
                    if (StringUtils.isEmpty(viewPath)) {
                        return;
                    }
                    ServletContext servletContext = request.getServletContext();
                    if (!viewPath.startsWith("/")) {
                        viewPath = "/" + viewPath;
                    }
                    RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(viewPath);
                    requestDispatcher.forward(request, response);
                } else if (controller instanceof RestController) {
                    // todo
                }
            }
        } catch (Throwable throwable) {
            if (throwable.getCause() instanceof IOException) {
                throw (IOException) throwable.getCause();
            } else {
                throw new ServletException(throwable.getCause());
            }
        }
    }

    private Set<String> findSupportedHttpMethod(Method method) {
        Set<String> supportedHttpMethods = new LinkedHashSet<>();
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotationFromMethod : annotations) {
            HttpMethod httpMethod = annotationFromMethod.annotationType().getAnnotation(HttpMethod.class);
            if (httpMethod != null) {
                supportedHttpMethods.add(httpMethod.value());
            }
        }
        if (supportedHttpMethods.isEmpty()) {
            supportedHttpMethods.addAll(Arrays.asList(
                    HttpMethod.DELETE,
                    HttpMethod.GET,
                    HttpMethod.HEAD,
                    HttpMethod.PUT,
                    HttpMethod.OPTIONS,
                    HttpMethod.POST));
        }
        return supportedHttpMethods;
    }
}
